//
//  TTXMLNode.m
//  Tint
//
//  Created by Kevin Wu on 4/19/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTXMLNode.h"

@interface TTXMLNode (Private)

- (void)releaseCachedValues;

@end

@implementation TTXMLNode (Private)

- (void)releaseCachedValues
{
    TTRelease(_cachedName);
    TTRelease(_cachedChildren);
    TTRelease(_cachedAttributes);
}

@end



@implementation TTXMLNode


#pragma mark - Accessors

@synthesize XMLNode = _XMLNode;

- (void)setXMLNode:(xmlNodePtr)XMLNode
{
    _XMLNode = XMLNode;
    [self releaseCachedValues];
}


- (NSString *)XMLString
{
    TTRelease(_XMLString);
    if ( _XMLNode ) {
        
        xmlBufferPtr buffer = xmlBufferCreate();
        if ( buffer ) {
            
            if ( xmlNodeDump(buffer, NULL, _XMLNode, 0, 0) > -1 ) {
                _XMLString = [[NSMutableString alloc] initWithBytes:(xmlBufferContent(buffer)) 
                                                             length:(xmlBufferLength(buffer)) 
                                                           encoding:NSUTF8StringEncoding];
            }
            xmlBufferFree(buffer);
            
        }
        
    }
    return _XMLString;
}


- (NSString *)name
{
    if ( _cachedName == nil ) {
        
        if ( _XMLNode ) {
            TTRelease(_cachedName);
            _cachedName = [[NSString alloc] initWithUTF8String:(const char *)(_XMLNode->name)];
        }
        
    }
    return _cachedName;
}

- (void)setName:(NSString *)name
{
    if ( _XMLNode && name ) {
        xmlNodeSetName(_XMLNode, (xmlChar *)[name UTF8String]);
        TTCopy(name, _cachedName);
    }
}


- (NSString *)value
{
    if ( _XMLNode ) {
        xmlChar *chars = xmlNodeGetContent(_XMLNode);
        if ( chars ) {
            NSString *value = [NSString stringWithUTF8String:(const char *)chars];
            xmlFree(chars);
            return value;
        }
    }
    return nil;
}

- (void)setValue:(NSString *)value
{
    if ( _XMLNode && value ) {
        xmlNodeSetContent(_XMLNode, (xmlChar *)[value UTF8String]);
    }
}


- (TTXMLNodeType)type
{
    if ( _XMLNode ) {
        switch ( _XMLNode->type ) {
            case XML_ELEMENT_NODE:         return TTXMLNodeElement;
            case XML_ATTRIBUTE_NODE:       return TTXMLNodeAttribute;
            case XML_TEXT_NODE:            return TTXMLNodeText;
            case XML_CDATA_SECTION_NODE:   return TTXMLNodeText;
            case XML_ENTITY_REF_NODE:      return TTXMLNodeEntityDeclaration;
            case XML_ENTITY_NODE:          return TTXMLNodeEntityDeclaration;
            case XML_PI_NODE:              return TTXMLNodeProcessingInstruction;
            case XML_COMMENT_NODE:         return TTXMLNodeComment;
            case XML_DOCUMENT_NODE:        return TTXMLNodeDocument;
            case XML_DOCUMENT_TYPE_NODE:   return TTXMLNodeDocument;
            case XML_DOCUMENT_FRAG_NODE:   return TTXMLNodeDocument;
            case XML_NOTATION_NODE:        return TTXMLNodeNotationDeclaration;
            case XML_HTML_DOCUMENT_NODE:   return TTXMLNodeDocument;
            case XML_DTD_NODE:             return TTXMLNodeDTD;
            case XML_ELEMENT_DECL:         return TTXMLNodeElementDeclaration;
            case XML_ATTRIBUTE_DECL:       return TTXMLNodeAttributeDeclaration;
            case XML_ENTITY_DECL:          return TTXMLNodeEntityDeclaration;
            case XML_NAMESPACE_DECL:       return TTXMLNodeNamespace;
            case XML_XINCLUDE_START:       return TTXMLNodeProcessingInstruction;
            case XML_XINCLUDE_END:         return TTXMLNodeProcessingInstruction;
            case XML_DOCB_DOCUMENT_NODE:   return TTXMLNodeDocument;
        }
    }
    return TTXMLNodeInvalid;
}




#pragma mark - Memory

- (id)initWithXMLNodePtr:(xmlNodePtr)XMLNode
{
    self = [super init];
    if ( self ) {
        _XMLNode = XMLNode;
    }
    return self;
}

- (void)dealloc
{
    _XMLNode = NULL;
    TTRelease(_XMLString);
    
    TTRelease(_cachedName);
    TTRelease(_cachedChildren);
    TTRelease(_cachedAttributes);
    [super dealloc];
}



#pragma mark - NSObject

- (BOOL)isEqual:(id)object
{
    if ( object ) {
        TTXMLNode *node = object;
        return ( self.XMLNode == node.XMLNode );
    }
    return NO;
}

- (NSString *)description
{
    return self.XMLString;
}



#pragma mark - Public

+ (TTXMLNode *)nodeWithXMLNodePtr:(xmlNodePtr)XMLNode
{
    return [[[self alloc] initWithXMLNodePtr:XMLNode] autorelease];
}



- (NSUInteger)childCount
{
    if ( _cachedChildren ) {
        return [_cachedChildren count];
    }
    
    if ( _XMLNode ) {
        
        NSUInteger count = 0;
        
        xmlNodePtr child = _XMLNode->children;
        
        while ( child ) {
            ++count;
            child = child->next;
        }
        return count;
    }
    return 0;
}

- (NSArray *)children
{
    if ( _cachedChildren ) {
        return _cachedChildren;
    }
    
    if ( _XMLNode && _XMLNode->children ) {
        
        TTRelease(_cachedChildren);
        _cachedChildren = [[NSMutableArray alloc] init];
        
        xmlNodePtr child = _XMLNode->children;
        while ( child ) {
            TTXMLNode *node = [[TTXMLNode alloc] initWithXMLNodePtr:child];
            [(NSMutableArray *)_cachedChildren addObject:node];
            TTRelease(node);
            child = child->next;
        }
        
        return _cachedChildren;
    }
    return nil;
}

- (TTXMLNode *)childAtIndex:(NSUInteger)index
{
    return [[self children] objectOrNilAtIndex:index];
}




- (NSArray *)elements
{
    NSMutableArray *elements = [[NSMutableArray alloc] init];
    
    for ( int i=0; i<[[self children] count]; ++i ) {
        TTXMLNode *node = [[self children] objectAtIndex:i];
        if ( node.type == TTXMLNodeElement ) {
            [elements addObject:node];
        }
    }
    
    if ( [elements count] < 1 ) {
        TTRelease(elements);
    }
    
    return [elements autorelease];
}

- (NSArray *)elementsForName:(NSString *)name
{
    NSMutableArray *elements = [[NSMutableArray alloc] init];
    
    for ( int i=0; i<[[self children] count]; ++i ) {
        TTXMLNode *node = [[self children] objectAtIndex:i];
        if ((node.type == TTXMLNodeElement) && 
            [node.name isEqualToString:name])
        {
            [elements addObject:node];
        }
    }
    
    if ( [elements count] < 1 ) {
        TTRelease(elements);
    }
    
    return [elements autorelease];
}

- (NSArray *)attributes
{
    if ( _cachedAttributes ) {
        return _cachedAttributes;
    }
    
    
    if ( _XMLNode && _XMLNode->properties ) {
        
        TTRelease(_cachedAttributes);
        _cachedAttributes = [[NSMutableArray alloc] init];
        
        xmlAttrPtr attr = _XMLNode->properties;
        while ( attr ) {
            TTXMLNode *node = [[TTXMLNode alloc] initWithXMLNodePtr:(xmlNodePtr)attr];
            [(NSMutableArray *)_cachedAttributes addObject:node];
            TTRelease(node);
            attr = attr->next;
        }
        
        return _cachedAttributes;
    }
    return nil;
}

- (TTXMLNode *)attributeForName:(NSString *)name
{
    if ( _XMLNode && _XMLNode->properties ) {
        xmlAttrPtr attr = _XMLNode->properties;
        while ( attr ) {
            if ( strcmp((const char *)(attr->name), [name UTF8String]) == 0 ) {
                return [TTXMLNode nodeWithXMLNodePtr:(xmlNodePtr)attr];
            }
            attr = attr->next;
        }
    }
    return nil;
}





#pragma mark - Tree

- (TTXMLNode *)parent
{
    if ( _XMLNode && _XMLNode->parent ) {
        return [TTXMLNode nodeWithXMLNodePtr:_XMLNode->parent];
    }
    return nil;
}



- (TTXMLNode *)firstChild
{
    if ( _XMLNode && _XMLNode->children ) {
        return [TTXMLNode nodeWithXMLNodePtr:_XMLNode->children];
    }
    return nil;
}

- (TTXMLNode *)firstChildNamed:(NSString *)name
{
    if ( _XMLNode && _XMLNode->children && name ) {
        xmlNodePtr node = _XMLNode->children;
        while ( node ) {
            if ( strcmp((const char *)(node->name), [name UTF8String]) == 0 ) {
                return [TTXMLNode nodeWithXMLNodePtr:node];
            }
            node = node->next;
        }
    }
    return nil;
}

- (TTXMLNode *)lastChild
{
    if ( _XMLNode && _XMLNode->last ) {
        return [TTXMLNode nodeWithXMLNodePtr:_XMLNode->last];
    }
    return nil;
}

- (TTXMLNode *)lastChildNamed:(NSString *)name
{
    if ( _XMLNode && _XMLNode->last && name ) {
        xmlNodePtr node = _XMLNode->last;
        while ( node ) {
            if ( strcmp((const char *)(node->name), [name UTF8String]) == 0 ) {
                return [TTXMLNode nodeWithXMLNodePtr:node];
            }
            node = node->prev;
        }
    }
    return nil;
}



- (TTXMLNode *)firstAttribute
{
    if ( _XMLNode && _XMLNode->properties ) {
        return [TTXMLNode nodeWithXMLNodePtr:(xmlNodePtr)(_XMLNode->properties)];
    }
    return nil;
}

- (TTXMLNode *)lastAttribute
{
    if ( _XMLNode && _XMLNode->properties ) {
        xmlAttrPtr attr = _XMLNode->properties;
        while ( attr->next ) {
            attr = attr->next;
        }
        return [TTXMLNode nodeWithXMLNodePtr:(xmlNodePtr)(attr)];
    }
    return nil;
}



- (TTXMLNode *)previousSibling
{
    if ( _XMLNode && _XMLNode->prev ) {
        return [TTXMLNode nodeWithXMLNodePtr:_XMLNode->prev];
    }
    return nil;
}

- (TTXMLNode *)previousSameNameSibling
{
    if ( _XMLNode ) {
        xmlNodePtr node = _XMLNode->prev;
        while ( node ) {
            if ( strcmp((const char *)(node->name), (const char *)(_XMLNode->name)) == 0 ) {
                return [TTXMLNode nodeWithXMLNodePtr:node];
            }
            node = node->prev;
        }
    }
    return nil;
}

- (TTXMLNode *)nextSibling
{
    if ( _XMLNode && _XMLNode->next ) {
        return [TTXMLNode nodeWithXMLNodePtr:_XMLNode->next];
    }
    return nil;
}

- (TTXMLNode *)nextSameNameSibling
{
    if ( _XMLNode ) {
        xmlNodePtr node = _XMLNode->next;
        while ( node ) {
            if ( strcmp((const char *)(node->name), (const char *)(_XMLNode->name)) == 0 ) {
                return [TTXMLNode nodeWithXMLNodePtr:node];
            }
            node = node->next;
        }
    }
    return nil;
}



#pragma mark - Create

- (TTXMLNode *)addElementWithName:(NSString *)name value:(NSString *)value
{
    if ( name && value ) {
        
        xmlNodePtr element = xmlNewNode(NULL, (xmlChar *)[name UTF8String]);
        
        if ( element ) {
            
            xmlNodePtr text = xmlNewText((xmlChar *)[value UTF8String]);
            
            if ( text ) {
                if ( xmlAddChild(element, text) ) {
                    return [self addChild:[TTXMLNode nodeWithXMLNodePtr:element]];
                }
                
                xmlFreeNode(text);
            }
            
            xmlFreeNode(element);
        }
        
    }
    return nil;
}

- (TTXMLNode *)addAttributeWithName:(NSString *)name value:(NSString *)value
{
    if ( name && value ) {
        xmlAttrPtr attr = xmlNewProp(NULL, (xmlChar *)[name UTF8String], (xmlChar *)[value UTF8String]);
        if ( attr ) {
            return [self addChild:[TTXMLNode nodeWithXMLNodePtr:(xmlNodePtr)attr]];
        }
    }
    return nil;
}

- (TTXMLNode *)addTextWithValue:(NSString *)value
{
    if ( value ) {
        xmlNodePtr text = xmlNewText((xmlChar *)[value UTF8String]);
        if ( text ) {
            return [self addChild:[TTXMLNode nodeWithXMLNodePtr:text]];
        }
    }
    return nil;
}

- (TTXMLNode *)addChild:(TTXMLNode *)child
{
    if ( _XMLNode && child.XMLNode ) {
        
        [self releaseCachedValues];
        
        xmlNodePtr newNode = NULL;
        
        if ( child.type == TTXMLNodeAttribute ) {
            // Ignore this if an attribute with the name is already present
            if ( xmlHasProp(_XMLNode, child.XMLNode->name) == NULL ) {
                newNode = child.XMLNode;
            }
            
        } else {
            newNode = child.XMLNode;
        }
        
        if ( newNode ) {
            if ( xmlAddChild(_XMLNode, newNode) ) {
                return [TTXMLNode nodeWithXMLNodePtr:child.XMLNode];
            } else {
                xmlFreeNode(child.XMLNode);
                child.XMLNode = NULL;
            }
        }
        
    }
    return nil;
}

- (void)removeChild:(TTXMLNode *)child
{
    if ((_XMLNode) && 
        (child.XMLNode->parent == _XMLNode))
    {
        [self releaseCachedValues];
        
        xmlUnlinkNode(child.XMLNode);
        xmlFreeNode(child.XMLNode);
        child.XMLNode = NULL;
    }
}

@end
